﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using SimpleCalcDataStructures;
using System.IO;
using System.Text;

namespace SimpleCalcParser
{
    public class InfixToPostFixConverter
    {
     
        #region Fields
        private Stack<Token> Operators;
        private static Dictionary<char, TokenType> TypeDictionary = new Dictionary<char, TokenType>();
        #endregion
        #region Constructor
        public InfixToPostFixConverter()
        {
            Operators = new Stack<Token>();
            if (TypeDictionary.Count == 0)
            {
                IntializeDict();
            }
        }

        private void IntializeDict()
        {
            TypeDictionary['0'] = TokenType.Number;
            TypeDictionary['1'] = TokenType.Number;
            TypeDictionary['2'] = TokenType.Number;
            TypeDictionary['3'] = TokenType.Number;
            TypeDictionary['4'] = TokenType.Number;
            TypeDictionary['5'] = TokenType.Number;
            TypeDictionary['6'] = TokenType.Number;
            TypeDictionary['7'] = TokenType.Number;
            TypeDictionary['8'] = TokenType.Number;
            TypeDictionary['9'] = TokenType.Number;
            TypeDictionary['.'] = TokenType.Number;
            TypeDictionary['e'] = TokenType.Number;
            TypeDictionary['π'] = TokenType.Number;
            TypeDictionary['+'] = TokenType.LeftAssocitiveBinaryOperator;
            TypeDictionary['−'] = TokenType.LeftAssocitiveBinaryOperator;
            TypeDictionary['*'] = TokenType.LeftAssocitiveBinaryOperator;
            TypeDictionary['/'] = TokenType.LeftAssocitiveBinaryOperator;
            TypeDictionary['^'] = TokenType.RightAssocitiveBinaryOperator;
            TypeDictionary['-'] = TokenType.UnaryOperator;
            TypeDictionary['s'] = TokenType.PrimaryStart;
            TypeDictionary['c'] = TokenType.PrimaryStart;
            TypeDictionary['t'] = TokenType.PrimaryStart;
            TypeDictionary['√'] = TokenType.PrimaryStart;
            TypeDictionary['('] = TokenType.PrimaryStart;
            TypeDictionary[')'] = TokenType.PrimaryEnd;
        }
        #endregion
        #region Public Methods
        public LinkedList<Token> Convert(String infixString)
        {
            LinkedList<Token> tokens = new LinkedList<Token>();
            using (StringReader input = new StringReader(infixString)){
                TokenType previousType = TokenType.LeftAssocitiveBinaryOperator;
                while (input.Peek() != -1)
                {
                    Token next = GetNext(input);
                    switch (next.type)
                    {
                        case TokenType.Number:
                          
                            if((previousType == TokenType.Number ||
                                previousType == TokenType.PrimaryEnd))
                                AddLeftBinaryAssocative(tokens, MakeMult());
                            tokens.AddLast(next);
                            
                            break;
                        case TokenType.UnaryOperator:
                            AddLeftBinaryAssocative(tokens, MakeMult());
                            Token neg = new Token();
                            neg.type = TokenType.Number;
                            neg.value = "-1";
                            tokens.AddLast(neg);
                            break;
                        case TokenType.LeftAssocitiveBinaryOperator:
                            AddLeftBinaryAssocative(tokens, next);
                            break;
                        case TokenType.RightAssocitiveBinaryOperator:
                            while (Operators.Count > 0 && Operators.Peek().type != TokenType.PrimaryStart && Precedence(next.value) < Precedence(Operators.Peek().value))
                            {
                                tokens.AddLast(Operators.Pop());
                            }
                            Operators.Push(next);
                            break;
                        case TokenType.PrimaryStart:
                            next.prevWasNumber = previousType == TokenType.Number ||
                                previousType == TokenType.PrimaryEnd;
                            Operators.Push(next);
                            break;
                        case TokenType.PrimaryEnd:
                            while (Operators.Peek().type != TokenType.PrimaryStart)
                            {
                                tokens.AddLast(Operators.Pop());
                            }
                            bool impiledMult = Operators.Peek().prevWasNumber;
                         
                            if (Operators.Peek().value != "(")
                            {
                                tokens.AddLast(Operators.Pop());
                            }
                            else
                            {
                                Operators.Pop();
                            }
                            if (impiledMult)
                            {
                                AddLeftBinaryAssocative(tokens, MakeMult());
                            }
                            break;
                    }
                    previousType = next.type;
                }
            }
            while (Operators.Count != 0)
            {
                tokens.AddLast(Operators.Pop());
            }
            return tokens;
        }

        private void AddLeftBinaryAssocative(LinkedList<Token> tokens, Token next)
        {
            while (Operators.Count > 0 && Operators.Peek().type != TokenType.PrimaryStart &&Precedence(next.value) <= Precedence(Operators.Peek().value))
            {
                tokens.AddLast(Operators.Pop());
            }

            Operators.Push(next);
        }

        private Token MakeMult()
        {
            Token mult = new Token();
            mult.type = TokenType.LeftAssocitiveBinaryOperator;
            mult.value = "*";
            return mult;
        }

        private int Precedence(string p)
        {
            switch (p[0])
            {
                case '+':
                case '−':
                    return 2;
                case '/':
                case '*':
                case '-':
                    return 3;
                case '^' :
                    return 4;
                case'(':
                    return 80;
                default:
                    return 0;
            }
        }

        private Token GetNext(StringReader input)
        {
            Token ret = new Token();
            char cur = (char) input.Read();
            ret.type = TypeDictionary[cur];
            ret.value = cur.ToString();
            if (ret.type == TokenType.Number)
            {
                HandleNumbers(input, ret, cur);
            }
            else if (ret.type == TokenType.PrimaryStart)
            {
                HandlePrimaryStart(input, ret, cur);   
            }
          
            return ret;
        }

       

        private void HandlePrimaryStart(StringReader input, Token ret, char cur)
        {
            switch (cur)
            {
                case 's':
                    ret.value = "sin";
                    input.Read();
                    input.Read();
                    input.Read();
                    break;
                case 'c':
                    ret.value = "cos";
                    input.Read();
                    input.Read();
                    input.Read();
                    break;
                case 't':
                    ret.value = "tan";
                    input.Read();
                    input.Read();
                    input.Read();
                    break;
                case '(':
                    break;
                case '√':
                    input.Read();
                    break;
                default:
                    break;
            }
        }

        private static void HandleNumbers(StringReader input, Token ret, char cur)
        {
            if (Char.IsDigit(cur))
            {
                bool NoPeriod = ret.value[0] != '.';
                char next = (char)input.Peek();
                while (next >= '0' && next <= '9' || next == '.')
                {
                    if (NoPeriod == false && next == '.')
                    {
                        throw new Exception("to many periods");
                    }
                    NoPeriod &= next != '.';
                    ret.value += next;

                    input.Read();
                    next = (char)input.Peek();
                }
            }
        }
        #endregion


    }
}
